#pragma once

#include <thread>
#include <mutex>
#include <condition_variable>
#include <future>
#include <functional>
#include <vector>
#include <queue>
#include <algorithm>

namespace NahidaProject {
	namespace ThreadPool {
		template <typename T> class __declspec(dllexport) ThreadPool {
		public:
			size_t Count() const noexcept {
				return GetUnderlying().count();
			}

			template<typename TFunc, typename... TArgs>
			auto Enqueue(TFunc task, TArgs &&... args) -> std::future<decltype(task(args...))> {
				return GetUnderlying().template enqueue(task, args...);
			}

		protected:
			void Start(size_t num_threads) {
				GetUnderlying().start();
			}

			void Stop() {
				GetUnderlying().stop();
			}

		private:
			inline auto& GetUnderlying() { return static_cast<T&>(*this); }
		};

		namespace Queue {
			class __declspec(dllexport) ThreadPool : public NahidaProject::ThreadPool::ThreadPool<NahidaProject::ThreadPool::Queue::ThreadPool> {
				using task_t = std::function<void()>;

			public:
				ThreadPool() {
					const auto num_threads{ std::max(std::thread::hardware_concurrency(), 2u) - 1u };
					Start(num_threads);
				}

				explicit ThreadPool(size_t num_threads) {
					Start(num_threads);
				}

				~ThreadPool() {
					Stop();
				}

				ThreadPool(const ThreadPool&) = delete;
				ThreadPool(ThreadPool&&) = delete;
				ThreadPool& operator=(const ThreadPool&) = delete;
				ThreadPool& operator=(ThreadPool&&) = delete;

				size_t Count() const noexcept {
					return _threads.size();
				}

				template<typename TFunc, typename... TArgs> auto Enqueue(TFunc task, TArgs &&...args) -> std::future<decltype(task(args...))> {
					auto func = std::bind(std::forward<TFunc>(task), std::forward<TArgs>(args)...);
					auto wrapper = std::make_shared<std::packaged_task<decltype(task(args...)) ()>>(func);

					{
						std::unique_lock<std::mutex>(_mutex);
						_tasks.emplace([wrapper] { (*wrapper)(); });
					}

					_event.notify_one();

					return wrapper->get_future();
				}

			private:
				void Start(size_t num_threads);
				void Stop() noexcept;

				std::vector<std::thread> _threads;
				std::condition_variable _event;
				std::mutex _mutex;
				bool _stopping{ false };
				std::queue<task_t> _tasks;
			};

		}

		namespace SafeQueue {

			namespace Detail {
				template <typename T> class __declspec(dllexport) Queue {
				public:
					void Push(T value) {
						std::lock_guard<std::mutex> lock(_mutex);
						_queue.push(value);
						_condition.notify_one();
					}

					std::shared_ptr<T> Pop() {
						std::unique_lock<std::mutex> lock(_mutex);
						_condition.wait(lock, [this] { return !_queue.empty(); });
						auto value = std::make_shared<T>(_queue.front());
						_queue.pop();
						return value;
					}

					bool Empty() const {
						std::unique_lock<std::mutex> lock(_mutex);
						return _queue.empty();
					}

				private:
					std::queue<T> _queue;
					mutable std::mutex _mutex;
					std::condition_variable _condition;
				};

			}

			class __declspec(dllexport) ThreadPool : public NahidaProject::ThreadPool::ThreadPool<NahidaProject::ThreadPool::SafeQueue::ThreadPool> {
				using task_t = std::function<void()>;

			public:
				ThreadPool() {
					const auto num_threads{ std::max(std::thread::hardware_concurrency(), 2u) - 1u };
					Start(num_threads);
				}

				explicit ThreadPool(size_t num_threads) {
					Start(num_threads);
				}

				~ThreadPool() {
					Stop();
				}

				ThreadPool(const ThreadPool&) = delete;
				ThreadPool(ThreadPool&&) = delete;
				ThreadPool& operator=(const ThreadPool&) = delete;
				ThreadPool& operator=(ThreadPool&&) = delete;

				size_t Count() const noexcept {
					return _threads.size();
				}

				template<typename TFunc, typename... TArgs> auto Enqueue(TFunc task, TArgs &&...args) -> std::future<decltype(task(args...))> {
					auto func = std::bind(std::forward<TFunc>(task), std::forward<TArgs>(args)...);
					auto wrapper = std::make_shared<std::packaged_task<decltype(task(args...)) ()>>(func);

					_tasks.Push(std::move([wrapper] { (*wrapper)(); }));

					_event.notify_one();

					return wrapper->get_future();
				}

			private:
				void Start(size_t num_threads);
				void Stop() noexcept;

				std::vector<std::thread> _threads;
				std::condition_variable _event;
				std::mutex _mutex;
				bool _stopping{ false };
				NahidaProject::ThreadPool::SafeQueue::Detail::Queue <task_t> _tasks;
			};

		}
	}
}

